package com.yexx.starter.optms.idempotent;

import com.google.common.base.Splitter;
import com.yexx.starter.optms.idempotent.exception.IdempotentException;
import com.yexx.starter.optms.idempotent.interceptor.IdempotentInterceptor;
import com.yexx.starter.optms.idempotent.keygen.impl.*;
import com.yexx.starter.optms.idempotent.template.LockTemplate;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;

/**
 * description:启动配置
 *
 * @author: zuomin (myleszelic@outlook.com)
 * @date 2022/06/01:22:09
 */
@Slf4j
@Configuration
@EnableConfigurationProperties(value = {IdempotentProperties.class, DistributeLockProperties.class})
public class IdempotentConfiguration {

    @Resource
    private IdempotentProperties idempotentProperties;
    @Resource
    private DistributeLockProperties lockProperties;

    @Resource
    private RedissonClient redissonClient;

    @Bean
    public CookieLockKeyResolver cookieLockKeyResolver() {
        return new CookieLockKeyResolver(idempotentProperties);
    }

    @Bean
    public HeaderLockKeyResolver headerLockKeyResolver() {
        return new HeaderLockKeyResolver(idempotentProperties);
    }

    @Bean
    public IpLockKeyResolver ipLockKeyResolver() {
        return new IpLockKeyResolver();
    }

    @Bean
    public SessionIdLockKeyResolver sessionIdLockKeyResolver() {
        return new SessionIdLockKeyResolver();
    }

    @Bean
    public DefaultLockKeyResolver defaultLockKeyResolver(HeaderLockKeyResolver headerLockKeyResolver,
                                  SessionIdLockKeyResolver sessionIdLockKeyResolver,
                                  CookieLockKeyResolver cookieLockKeyResolver) {
        return new DefaultLockKeyResolver(headerLockKeyResolver, sessionIdLockKeyResolver, cookieLockKeyResolver);
    }

    @Bean
    public IdempotentInterceptor idempotentInterceptor() {
        return new IdempotentInterceptor(idempotentProperties, redissonClient);
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "app.lock", value = "enable", havingValue = "true")
    public LockTemplate lockTemplate() {
        return new LockTemplate(lockProperties, redissonClient);
    }

    /**
     * 增加拦截器处理
     */
    @Configuration
    @Order
    @ConditionalOnProperty(prefix = "app.idempotent", value = "manual-setting-idempotent-interceptor", havingValue = "true", matchIfMissing = true)
    public static class IdempotentWebConfig implements WebMvcConfigurer {

        @Resource
        private IdempotentInterceptor idempotentInterceptor;

        @Resource
        private IdempotentProperties idempotentProperties;

        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            List<String> includeUrls = new ArrayList<>();
            if (StringUtils.hasText(idempotentProperties.getIncludeUrls())) {
                includeUrls = Splitter.on(",").trimResults().splitToList(idempotentProperties.getIncludeUrls());
            }
            List<String> excludeUrls = new ArrayList<>();
            if (StringUtils.hasText(idempotentProperties.getExcludeUrls())) {
                excludeUrls = Splitter.on(",").trimResults().splitToList(idempotentProperties.getExcludeUrls());
            }

            /**
             * {@link InterceptorRegistry#getInterceptors() 这里的排序规则是根据InterceptorRegistration 的order}
             */
            InterceptorRegistration registration = registry.addInterceptor(idempotentInterceptor);
            registration.order(idempotentInterceptor.getOrder());
            registration.excludePathPatterns(excludeUrls);
            registration.addPathPatterns(includeUrls);

            log.info("register idempotent interceptor success :)");
        }

    }

    /**
     * 异常处理
     */
    @ControllerAdvice
    @Order(value = Ordered.LOWEST_PRECEDENCE - 100)
    @Controller
    @Slf4j
    public static class IdempotentExceptionConfiguration {

        @Resource
        private HttpServletRequest httpServletRequest;

        /** 自定义返回错误 */
        String errReturn = "{\"code\":%s, \"message\":\"%s\"}";

        @ExceptionHandler(value = {IdempotentException.class})
        @ResponseBody
        public ResponseEntity<String> idempotentExceptionHandler(IdempotentException idempotentException) {
            log.info("idempotent requestUrl={} sessionId={}", httpServletRequest.getRequestURI(), httpServletRequest.getSession().getId());
            Integer code = idempotentException.getErrCode();
            String message = idempotentException.getMessage();
            String format = String.format(errReturn, code, message);
            return ResponseEntity.ok(format);
        }
    }

}
